Status: Spec complete. Nothing built yet except tracking columns in D1 and worker ref writes. Portal, redirect worker, blast system — all pending. Last updated: May 2026 Read this before any affiliate or reseller session. Do not infer. Do not guess.
{tp}.claritysystems.workaffiliates.claritysystems.work/r/[random-code]| Decision | Chosen | Reason |
|---|---|---|
| Portal location | affiliates.claritysystems.work |
Not course-specific. One portal, all courses, forever. |
| ref code format | Random 6-char slug e.g. x7k2mp |
Privacy between affiliates. Not name-based. |
| Commission rate model | KV default floor, D1 override per affiliate | No self-selection or categories. Lovinia controls all rates. |
| Tracking link format | Permanent redirect via /r/[ref] |
Captures impressions (clicks) + conversions. Link never changes. |
| Redirect destination | One KV entry affiliate:redirect controls all links |
Update one entry = all links point to new course instantly |
| Blast trigger | /blast command to Telegram bot |
Lovinia sends one message, bot handles the rest. Private, no channel. |
| Blast content — Telegram | Image from R2 + personal link in message body | Lovinia uploads new image to R2 per course, updates one KV entry |
| Blast content — Email | Plain email + PDF attached from R2 + HTML template from KV | Template never redesigned. Only vars change per course. |
| Email template storage | KV (text, injected with vars at send time) | Faster than R2 fetch. Worker injects vars before sending via Resend. |
| PDF storage | R2 | Binary file. One upload per course. URL stored in KV. |
| Affiliate dashboard | Not built | Build when 3+ active affiliates ask for it. |
| Telegram notifications | Bot messages Lovinia on every conversion. Messages affiliate only if Chat ID provided. | Not all affiliates are Telegram users. |
| Affiliate Telegram Chat ID | Optional at signup | Affiliates who post on social or WhatsApp and aren't Telegram-savvy still tracked and notified by email. |
| Conversion notification to affiliate | Telegram if Chat ID provided + email always | Worker: if telegram_chat_id exists → send Telegram. Send email regardless. |
| Affiliate privacy | One-to-one bot messages only. No group. No channel. | Affiliates never see each other. |
| Link delivery on signup | Email only. Not shown on page. | Email is permanent record. Eliminates "I lost my link" support. |
| Reseller affiliate sub-model | Out of scope | TP's internal arrangement. Absorbed before paying Lovinia. |
enquiries table has ref and ref_channel columns — written on all form submissionsref + ref_channel from POST body and writes to D1 on all pathsstatus = stripe_redirect) before returning Stripe URLaffiliates table exists in claritysystems-db (base schema only — columns missing, see below)pricing/index.njk passes ?ref= to register links, HRDC grant link, and enquiry linkpricing-hrdc-grant-claim/index.njk passes ?ref= to all request-hrdc and enquiry linksref from URL and include in POST bodyaffiliates.claritysystems.work)/r/[ref])affiliates table missing columns: company, telegram_chat_id, commission_pctenquiries table missing columns: tier, is_earlybirdclicks table does not existtier + is_earlybird in POST bodyProblem: Card and FPX payment paths do not pass tier or is_earlybird to the worker. Commission calculation will be wrong without these.
Fix: Pass tier (single/duo/fivepax) and is_earlybird (true/false based on earlybird deadline) in POST body from register/index.njk on both card and FPX paths. Worker writes to D1.
Do this before portal launches. Conversion Telegram messages show commission amount — that number will be wrong without tier and price point.
SIGNUP
Affiliate visits affiliates.claritysystems.work
Reads page: what it is, how it works, what they earn, FAQ
Opens Telegram → messages bot → receives their Chat ID
Fills form: name, email, company, Chat ID
Submits
Worker: generates ref code → writes affiliates table → sends confirmation email → Telegrams Lovinia
Page: "Check your inbox."
Email arrives: their permanent link + full terms + current course details
SHARING
Affiliate shares affiliates.claritysystems.work/r/x7k2mp anywhere
Someone clicks it
Worker: logs click to `clicks` table → fetches affiliate:redirect from KV → redirects to [destination]?ref=x7k2mp
Visitor lands on pricing page with ref in URL
Ref passes through all links (pricing → register / enquiry / hrdc grant)
CONVERSION
Visitor submits any form or hits Stripe redirect
Worker: writes enquiry row with ref, tier, is_earlybird, form_type, hrdc_option
Worker: looks up affiliate by ref → gets commission_pct (D1 override or KV default)
Worker: looks up tier price from KV course data → calculates commission
Worker: Telegrams Lovinia (full details + commission amount)
Worker: Telegrams affiliate (what they chose + estimated commission + payment timeline)
NEW COURSE BLAST
Lovinia: uploads new Telegram image to R2 → updates affiliate:blast_image_url in KV
Lovinia: uploads new PDF to R2 → updates affiliate:blast_pdf_url in KV
Lovinia: updates KV blast vars (course name, dates, city, venue, earlybird price, deadline)
Lovinia: updates affiliate:redirect in KV to new course pricing page URL
Lovinia: sends /blast to Telegram bot
Bot: confirms it's Lovinia's Chat ID → fetches all active affiliates from D1
Bot: for each affiliate:
→ sends Telegram image + message with their personal link
→ sends email: HTML template (fetched from KV, vars injected) + PDF attached from R2
Every affiliate link already points to new course. No link update needed on their end.
All keys prefixed affiliate:. Set once, update per course where noted.
| Key | Value | When to update |
|---|---|---|
affiliate:redirect |
Current course pricing page URL | Every new course |
affiliate:default_commission_pct |
e.g. 10 |
When default rate changes |
affiliate:blast_image_url |
R2 public URL of Telegram course image | Every new course |
affiliate:blast_pdf_url |
R2 public URL of course PDF brochure | Every new course |
affiliate:blast_course_name |
e.g. Sales For Technical People |
Every new course |
affiliate:blast_dates |
e.g. June 17–18, 2026 |
Every new course |
affiliate:blast_city |
e.g. George Town, Penang |
Every new course |
affiliate:blast_venue |
e.g. E&O Hotel |
Every new course |
affiliate:blast_earlybird_price |
e.g. RM 2,800 |
Every new course |
affiliate:blast_earlybird_deadline |
e.g. 17 May 2026 |
Every new course |
email:affiliate_blast_template |
Full HTML email template string with `` | When design changes |
email:affiliate_signup_template |
Full HTML email template string for signup confirmation | When design changes |
Both templates share the same placeholder format: ``
Blast email vars: ``
Signup confirmation vars: ``
affiliates tableCREATE TABLE affiliates (
id TEXT PRIMARY KEY, -- random ref code e.g. 'x7k2mp'
name TEXT NOT NULL,
email TEXT NOT NULL,
company TEXT,
telegram_chat_id TEXT, -- optional. if null, notifications go to email only.
commission_pct REAL, -- null = use KV default
status TEXT NOT NULL DEFAULT 'active',
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
affiliates tableALTER TABLE affiliates ADD COLUMN company TEXT;
ALTER TABLE affiliates ADD COLUMN telegram_chat_id TEXT;
ALTER TABLE affiliates ADD COLUMN commission_pct REAL;
enquiries tableALTER TABLE enquiries ADD COLUMN tier TEXT;
ALTER TABLE enquiries ADD COLUMN is_earlybird INTEGER DEFAULT 0;
clicks table — create newCREATE TABLE clicks (
id TEXT PRIMARY KEY,
ref TEXT NOT NULL,
clicked_at TEXT NOT NULL DEFAULT (datetime('now')),
ip_hash TEXT,
user_agent TEXT
);
affiliates-worker (new, on affiliates.claritysystems.work)Routes:
GET /r/:ref — Click-tracking redirect
affiliate:redirect without ref.clicks table: id (UUID), ref, clicked_at, ip_hash (SHA-256 of IP), user_agentaffiliate:redirect from KV[destination]?ref=[ref]POST /signup — Affiliate registration
email:affiliate_signup_template from KV{ ok: true }POST /blast (Telegram bot webhook, Lovinia only)
/blast → trigger blast. Any other command → ignore.WHERE status = 'active')email:affiliate_blast_template)affiliate:blast_image_url) + text message with their link
c. Send email via Resend: subject, HTML body, PDF attached (fetched from affiliate:blast_pdf_url)trainingbiz-admin-clerk (existing — additions only)On any enquiry write where ref is not null:
affiliate.commission_pct ?? KV affiliate:default_commission_pcttier + is_earlybirdprice × rate / 100telegram_chat_id if present (not null). Always send conversion email to affiliate regardless.affiliate:default_commission_pct in KV (set once, e.g. 10)affiliates.commission_pct in D1, set per affiliate by Lovinia, null = use defaultconst rate = affiliate.commission_pct ?? parseFloat(await env.CAC_KV.get('affiliate:default_commission_pct'))Set default:
wrangler kv key put --namespace-id=<CAC_KV_ID> "affiliate:default_commission_pct" "10"
Override per affiliate:
UPDATE affiliates SET commission_pct = 15 WHERE id = 'x7k2mp';
🔗 New affiliate signed up
Name: [name]
Email: [email]
Company: [company or —]
Ref: [ref]
Link: affiliates.claritysystems.work/r/[ref]
💰 Affiliate conversion
Affiliate: [name] ([ref])
Lead: [contact_person] — [email]
Path: [form_type] / [hrdc_option]
Tier: [tier] — [Early Bird / Standard]
Est. value: RM [amount]
Est. commission: [rate]% → RM [calculated]
✅ Your link just converted
Someone registered via your link.
What they chose: [tier] — [Early Bird / Standard]
Estimated commission: RM [calculated]
We confirm and pay within [X days] after the event.
[Image: course Telegram graphic from R2]
🗓 [Course name] is open
[Dates] · [City] · [Venue]
Early bird: [price] — closes [deadline]
Your link (unchanged):
affiliates.claritysystems.work/r/[their ref]
Share it now. It already points to the new course.
✅ Blast sent to [n] affiliates.
These must be completed before any build session starts. The build session will implement, not decide.
affiliates.claritysystems.workSections to write:
Design reference: Match claritysystems.work aesthetic — JetBrains Mono, Cormorant Garamond, cold red, near black, clean.
Answers: Lovinia to provide before build session. Agent drafts, Lovinia approves.
Plain structure, HTML rendered:
Plain structure, HTML rendered:
All leads from one affiliate:
SELECT contact_person, email, company_name, form_type, hrdc_option, tier, is_earlybird, status, submitted_at
FROM enquiries WHERE ref = 'x7k2mp'
ORDER BY submitted_at DESC;
All affiliates — clicks, leads, conversions:
SELECT
a.id, a.name, a.email, a.commission_pct,
COUNT(DISTINCT c.id) as clicks,
COUNT(DISTINCT e.id) as leads,
SUM(CASE WHEN e.status = 'stripe_redirect' THEN 1 ELSE 0 END) as went_to_stripe,
SUM(CASE WHEN e.status = 'confirmed' THEN 1 ELSE 0 END) as confirmed,
SUM(CASE WHEN e.status = 'paid' THEN 1 ELSE 0 END) as paid
FROM affiliates a
LEFT JOIN clicks c ON c.ref = a.id
LEFT JOIN enquiries e ON e.ref = a.id
GROUP BY a.id ORDER BY clicks DESC;
Stripe redirects to cross-reference with Stripe:
SELECT ref, contact_person, email, tier, is_earlybird, submitted_at
FROM enquiries
WHERE ref IS NOT NULL AND status = 'stripe_redirect'
ORDER BY submitted_at DESC;
Mark confirmed and paid:
UPDATE enquiries SET status = 'confirmed' WHERE email = '[email protected]' AND ref = 'x7k2mp';
UPDATE enquiries SET status = 'paid' WHERE email = '[email protected]' AND ref = 'x7k2mp';
Skip the portal, the redirect worker, and the blast system entirely. The conversion tracking already works. Do this to get 5 links live immediately.
Step 1 — D1 migrations (Console, run once):
ALTER TABLE affiliates ADD COLUMN company TEXT;
ALTER TABLE affiliates ADD COLUMN telegram_chat_id TEXT;
ALTER TABLE affiliates ADD COLUMN commission_pct REAL;
Step 2 — Insert affiliates manually:
INSERT INTO affiliates (id, name, email, company, status) VALUES
('a1b2c3', 'Name One', '[email protected]', 'Company A', 'active'),
('d4e5f6', 'Name Two', '[email protected]', 'Company B', 'active'),
('g7h8i9', 'Name Three', '[email protected]', '', 'active'),
('j1k2l3', 'Name Four', '[email protected]', '', 'active'),
('m4n5o6', 'Name Five', '[email protected]', '', 'active');
Replace names, emails, ref codes with your actual data. Ref codes are 6 chars, any alphanumeric.
Step 3 — Their link (send directly, no portal):
https://claritysystems.work/sales-training/pricing/?ref=a1b2c3
Send each person their link via WhatsApp, email, whatever. Tell them to share it as-is.
Step 4 — Verify a conversion:
SELECT ref, contact_person, email, form_type, status, submitted_at
FROM enquiries WHERE ref IS NOT NULL ORDER BY submitted_at DESC;
What you lose vs full system: impression/click counting. What you keep: full conversion tracking, ref in every D1 enquiry row. Good enough to validate the system and pay affiliates.
Prerequisites before this session starts:
Build steps — in order. Do not skip.
ALTER TABLE affiliates ADD COLUMN company TEXT;
ALTER TABLE affiliates ADD COLUMN telegram_chat_id TEXT;
ALTER TABLE affiliates ADD COLUMN commission_pct REAL;
ALTER TABLE enquiries ADD COLUMN tier TEXT;
ALTER TABLE enquiries ADD COLUMN is_earlybird INTEGER DEFAULT 0;
CREATE TABLE clicks (
id TEXT PRIMARY KEY,
ref TEXT NOT NULL,
clicked_at TEXT NOT NULL DEFAULT (datetime('now')),
ip_hash TEXT,
user_agent TEXT
);
wrangler kv key put --namespace-id=<CAC_KV_ID> "affiliate:default_commission_pct" "[rate]"
wrangler kv key put --namespace-id=<CAC_KV_ID> "affiliate:redirect" "https://claritysystems.work/sales-training/pricing/"
wrangler kv key put --namespace-id=<CAC_KV_ID> "affiliate:blast_image_url" "[R2 URL]"
wrangler kv key put --namespace-id=<CAC_KV_ID> "affiliate:blast_pdf_url" "[R2 URL]"
wrangler kv key put --namespace-id=<CAC_KV_ID> "affiliate:blast_course_name" "[name]"
wrangler kv key put --namespace-id=<CAC_KV_ID> "affiliate:blast_dates" "[dates]"
wrangler kv key put --namespace-id=<CAC_KV_ID> "affiliate:blast_city" "[city]"
wrangler kv key put --namespace-id=<CAC_KV_ID> "affiliate:blast_venue" "[venue]"
wrangler kv key put --namespace-id=<CAC_KV_ID> "affiliate:blast_earlybird_price" "[price]"
wrangler kv key put --namespace-id=<CAC_KV_ID> "affiliate:blast_earlybird_deadline" "[date]"
# Upload HTML templates last — after copy is approved
wrangler kv key put --namespace-id=<CAC_KV_ID> "email:affiliate_signup_template" "$(cat signup-email.html)"
wrangler kv key put --namespace-id=<CAC_KV_ID> "email:affiliate_blast_template" "$(cat blast-email.html)"
Fix register page — pass tier and is_earlybird in POST body on card/FPX paths (register/index.njk)
Update trainingbiz-admin-clerk — add affiliate Telegram notification on conversion (look up telegram_chat_id by ref, send message)
Build affiliates-worker — new worker on affiliates.claritysystems.work:
GET /r/:ref — click tracking + redirectPOST /signup — affiliate registration, email, Telegram to LoviniaPOST /blast (Telegram webhook) — blast to all active affiliatesBuild affiliate portal static page — affiliates.claritysystems.work on Cloudflare Pages
Wire Telegram bot webhook to /blast endpoint
Live test:
?ref=/blast → confirm all active affiliates receive Telegram + email[ ] Design Telegram image → upload to R2 → copy public URL
[ ] Design PDF brochure → upload to R2 → copy public URL
[ ] Update KV: affiliate:blast_image_url
[ ] Update KV: affiliate:blast_pdf_url
[ ] Update KV: affiliate:blast_course_name
[ ] Update KV: affiliate:blast_dates
[ ] Update KV: affiliate:blast_city
[ ] Update KV: affiliate:blast_venue
[ ] Update KV: affiliate:blast_earlybird_price
[ ] Update KV: affiliate:blast_earlybird_deadline
[ ] Update KV: affiliate:redirect ← new course pricing page URL
[ ] Send /blast to bot
[ ] Done. All affiliate links now point to new course. All affiliates notified.
| Depends on | Why |
|---|---|
claritysystems-db D1 |
All affiliate, enquiry, click data |
CAC_KV |
Commission rates, redirect destination, blast vars, email templates |
| R2 | Telegram image and PDF per course |
trainingbiz-admin-clerk worker |
Enquiry writes, conversion Telegram to affiliate |
affiliates-worker (new) |
Redirect, signup, blast |
| Resend | All affiliate emails |
| Telegram bot | All notifications — Lovinia and affiliates |
| Cloudflare Pages | Hosts affiliate portal static page |
Chatgpt update :
## Affiliate Tracking Build Fixes — May 2026
Today we fixed the affiliate tracking system end-to-end.
The core issue was not one single bug. It was a chain of deployment, routing, layout, DNS, and Worker configuration problems.
---
## 1. Fixed the wrong Worker being recreated
Problem:
GitHub/Wrangler kept recreating the wrong Worker because the repo config still said:
```toml
name = "affiliates-worker"
But the actual intended Worker in Cloudflare was:
affilliates-worker
Fix:
Updated:
workers/affiliates-worker/wrangler.toml
to:
name = "affilliates-worker"
main = "src/index.js"
compatibility_date = "2024-09-23"
workers_dev = false
Result:
Deploys now go to the correct Cloudflare Worker instead of recreating the deleted/wrong Worker.
Problem:
The Worker threw Cloudflare Error 1101 because it used relative redirects like:
Response.redirect("/admin/login", 302)
Response.redirect("/admin", 302)
Cloudflare Worker runtime complained:
Unable to parse URL: /admin/login
Fix:
Changed relative redirects to absolute redirects:
Response.redirect(new URL("/admin/login", request.url).toString(), 302)
Response.redirect(new URL("/admin", request.url).toString(), 302)
Also changed response headers like:
Location: "/admin"
to:
Location: new URL("/admin", request.url).toString()
File changed:
workers/affiliates-worker/src/index.js
Result:
Admin login now works.
Confirmed the required D1 tables exist:
affiliates
clicks
Confirmed affiliates has the needed working columns:
id
name
phone
email
company
status
created_at
Result:
The admin panel can create affiliate records, and the Worker can look up active affiliate refs.
Correct test command:
curl -s -D - -o /dev/null https://affiliates.claritysystems.work/r/ckl244
Important: do not use curl -I.
Reason:
curl -I
sends a HEAD request.
The Worker route only handles:
if (request.method === "GET" && url.pathname.startsWith("/r/"))
So curl -I returns 404 even when the browser/GET route works.
Correct result:
HTTP/2 302
location: https://claritysystems.work/sales-training/?ref=ckl244
Result:
The affiliate redirect route works.
Problem:
Affiliate links correctly landed with:
?ref=ckl244
But when visitors clicked other pages, the ref disappeared.
Initial wrong assumption:
Adding the script only to:
_includes/base.njk
was not enough.
Reason:
The site uses mixed architecture:
Articles use _includes/base.njk
Pricing page is standalone: sales-training/pricing/index.njk
Sales pages are standalone .njk files
Home page is plain index.html
So the script had to be added to all standalone pages too.
Files patched included:
_includes/base.njk
sales-training/pricing/index.njk
sales-training/pricing-hrdc-grant-claim/index.njk
sales-training/register/index.njk
sales-training/enquiry/index.njk
sales-training/request-hrdc-grant-claim/index.njk
sales-training/index.njk
sales-training/about/index.njk
sales-training/about/Lovinia.njk
sales-training/articles/index.njk
resellers/newrise/articles/index.njk
index.html
Script purpose:
1. Read ?ref= and ?ch= from landing URL
2. Save them into localStorage and cookie
3. Append ref/ch to internal same-origin links
4. Skip external links, mailto, tel, anchors, and javascript links
Core script added:
<script>
// Affiliate ref persistence — runs on every page
(function () {
const params = new URLSearchParams(window.location.search);
const refFromUrl = params.get('ref');
const chFromUrl = params.get('ch');
if (refFromUrl) {
localStorage.setItem('aff_ref', refFromUrl);
document.cookie = 'aff_ref=' + encodeURIComponent(refFromUrl) + '; Path=/; Max-Age=' + (60 * 60 * 24 * 30) + '; SameSite=Lax';
}
if (chFromUrl) {
localStorage.setItem('aff_ch', chFromUrl);
document.cookie = 'aff_ch=' + encodeURIComponent(chFromUrl) + '; Path=/; Max-Age=' + (60 * 60 * 24 * 30) + '; SameSite=Lax';
}
const ref = refFromUrl || localStorage.getItem('aff_ref') || '';
const ch = chFromUrl || localStorage.getItem('aff_ch') || '';
if (!ref) return;
document.querySelectorAll('a[href]').forEach(function (a) {
try {
const href = a.getAttribute('href');
if (!href) return;
if (href.startsWith('#')) return;
if (href.startsWith('mailto:')) return;
if (href.startsWith('tel:')) return;
if (href.startsWith('javascript:')) return;
const linkUrl = new URL(href, window.location.origin);
if (linkUrl.origin !== window.location.origin) return;
linkUrl.searchParams.set('ref', ref);
if (ch) linkUrl.searchParams.set('ch', ch);
a.href = linkUrl.toString();
} catch (e) {}
});
})();
</script>
Verification commands:
curl -s "https://claritysystems.work/?ref=ckl244&v=$(date +%s)" | grep "Affiliate ref persistence" || echo "HOME MISSING"
curl -s "https://claritysystems.work/sales-training/?ref=ckl244&v=$(date +%s)" | grep "Affiliate ref persistence" || echo "SALES TRAINING MISSING"
curl -s "https://claritysystems.work/sales-training/pricing/?ref=ckl244&v=$(date +%s)" | grep "Affiliate ref persistence" || echo "PRICING MISSING"
Final result:
All main pages now include the persistence script.
Original generated affiliate links looked like:
https://affiliates.claritysystems.work/r/ckl244
Concern:
The word affiliates made the referral nature too obvious in the shared URL.
New structure:
Admin / portal:
https://affiliates.claritysystems.work/admin
Public tracking link:
https://go.claritysystems.work/r/ckl244
Destination:
https://claritysystems.work/sales-training/?ref=ckl244
Cloudflare DNS added:
Type: CNAME
Name: go
Target: claritysystems.work
Proxy: Proxied / orange cloud
TTL: Auto
Cloudflare Worker route added:
go.claritysystems.work/*
attached to:
affilliates-worker
Important: kept existing route too:
affiliates.claritysystems.work/*
Result:
Both routes can work, but admin-generated public links now use the cleaner go subdomain.
Problem:
Admin panel hardcoded generated links as:
const link = "https://affiliates.claritysystems.work/r/" + a.id;
Fix:
Changed to:
const link = "https://go.claritysystems.work/r/" + a.id;
File changed:
workers/affiliates-worker/src/index.js
Terminal command used:
perl -pi -e 's#https://affiliates\.claritysystems\.work/r/#https://go.claritysystems.work/r/#g' workers/affiliates-worker/src/index.js
Verification:
grep -n "claritysystems.work/r/" workers/affiliates-worker/src/index.js
Expected:
const link = "https://go.claritysystems.work/r/" + a.id;
Result:
The admin panel now generates:
https://go.claritysystems.work/r/ckl244
Problem:
The new go link worked when DNS was forced, but the browser showed:
DNS_PROBE_FINISHED_NXDOMAIN
and terminal showed:
curl: (6) Could not resolve host: go.claritysystems.work
Diagnosis:
Cloudflare DNS was working:
dig @1.1.1.1 go.claritysystems.work
returned Cloudflare IPs.
But local Mac/router DNS was stale:
nslookup go.claritysystems.work
used:
Server: 192.168.100.1
and returned no answer.
Fix:
Changed Mac Wi-Fi DNS via terminal:
sudo networksetup -setdnsservers "Wi-Fi" 1.1.1.1 1.0.0.1 8.8.8.8
Verified:
networksetup -getdnsservers "Wi-Fi"
Expected:
1.1.1.1
1.0.0.1
8.8.8.8
Then flushed DNS:
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder
Result:
go.claritysystems.work resolved in browser and redirected correctly.
Current final flow:
Lovinia logs into admin:
https://affiliates.claritysystems.work/admin
Admin generates public affiliate link:
https://go.claritysystems.work/r/ckl244
Visitor clicks link.
Worker logs click and redirects to:
https://claritysystems.work/sales-training/?ref=ckl244
Visitor clicks around internal pages.
Ref persists as:
?ref=ckl244
Visitor submits enquiry / registration.
Form and Worker can read ref for D1 tracking and affiliate attribution.
Relevant commits:
fix affiliates worker relative redirects
rename worker to affilliates-worker
persist affiliate ref across internal site links
add affiliate ref persistence to standalone sales pages
add affiliate ref persistence across all standalone pages
add affiliate ref persistence to home page
use go subdomain for public affiliate links
Working:
Affiliate admin login
Affiliate creation
Generated affiliate links
go.claritysystems.work redirect route
D1 affiliate lookup
Click redirect
Ref persistence across main internal pages
Mac DNS resolution after changing DNS servers
Still future work:
Affiliate dashboard reporting
Commission calculation
Telegram affiliate notifications
Email blast
Portal signup
Full affiliate conversion dashboard